package vivace.model;

import java.util.*;
import javax.sound.midi.*;
import java.io.*;

public class Project extends Observable {
	
	/**
	 * Fields
	 */

	
	private Sequencer sequencer = null;	// the project's sequencer
	private static Synthesizer synthesizer = null;
	
	/**
	 * Enumerator that explains the supported operations for this class.	
	 */
	public static enum Action{
		SAVED, TRACK_ADDED, TRACK_REMOVED, PLAYING, STOPPED // osv...
	}
	
	private Action action;
	private String name;

    private long currentPosition; // Used for saving a position to return to when stoping a playing Project.

	/**
	 * @return The last performed operation for the current instance. This value can be used
	 * in the observer's update-method to see what kind of operation that was performed.
	 */
	public Action getAction() {
		return action;
	}
	
	/**
	 * @return The name of the project
	 */
	public String getName() {
		return name;
	}

	/**
	 * Sets the name of the project
	 * @param name
	 */
	public void setName(String name) {
		this.name = name;
	}

	/**
	 * Constructor
	 */
	public Project(){
	}
	
	public Project(Sequence sequence){
		try {
			sequencer = MidiSystem.getSequencer();
			sequencer.setSequence(sequence);
			sequencer.setTrackMute(0, true);
			
		} 
		catch (MidiUnavailableException e) {
			e.printStackTrace();
		}
		catch (InvalidMidiDataException e) {
			e.printStackTrace();
		}
	}
	
	public Project(File midiFile){

		Sequence sequence = null;

		//Create a sequence from the MIDI-File
		try
		{
			sequence = MidiSystem.getSequence(midiFile);
		}
		
		//In case of an exception, we dump the exception and the stack trace to the console.
		catch (InvalidMidiDataException e)
		{

			e.printStackTrace();
			System.exit(1);
		}
		
	

		//In case of an exception, we dump the exception and the stack trace to the console.
		catch (IOException e)
		{

			 e.printStackTrace();
			 System.exit(1);
		}

		//Get the default sequencer
		try {

			sequencer = MidiSystem.getSequencer();

		} 
		
		//In case of an exception, we dump the exception and the stack trace to the console.
		catch (MidiUnavailableException e) 
		{
			e.printStackTrace();
		}

		//If the system can't find a default sequencer, tell!
		if (sequencer == null)
		{
			System.out.println("SimpleMidiPlayer.main(): can't get a Sequencer");
			System.exit(1);
		}

		//The sequencer must be opened
		try
		{
			sequencer.open();
		}
		
		//In case of an exception, we dump the exception and the stack trace to the console.
		catch (MidiUnavailableException e)
		{
			e.printStackTrace();
			System.exit(1);
		}
		
		//Now we must let the sequencer know what sequence to play
		try
		{
			sequencer.setSequence(sequence);
		}
		
		//In case of an exception, we dump the exception and the stack trace to the console.
		catch (InvalidMidiDataException e)
		{
			e.printStackTrace();
			System.exit(1);
		}
		
		/*In older JDK sequence and synthesizer are combined in one object 
		 *if so nothing more have to be done
		 */
		if (! (sequencer instanceof Synthesizer))
		{

			//Try to get the default synthesizer
			try
			{
				synthesizer = MidiSystem.getSynthesizer();
				synthesizer.open();
				
				//Create a Transmitter and Reciever for synthesizer and sequencer
				Receiver synthReceiver = synthesizer.getReceiver();
				Transmitter	seqTransmitter = sequencer.getTransmitter();
				
				//Chain the sequencer and synthesizer with the created reciever and transmitter
				seqTransmitter.setReceiver(synthReceiver);
			}
			
			//In case of an exception, we dump the exception and the stack trace to the console.
			catch (MidiUnavailableException e)
			{
				e.printStackTrace();
			}
		}
		
	}

	
	
	/**
	 * Returns the sequencer
	 */
	
	public Sequencer getSequencer() {
		return sequencer;
    }



    /**
     * Plays the current file, saving the current play position for later use by stop().
     */

    public void play(){
        if(!this.sequencer.isRunning()) {
        	currentPosition=this.sequencer.getMicrosecondPosition();;
            this.sequencer.start();
            this.action=Action.PLAYING;
            this.notifyObservers();
        }
    }

    /**
     * Stops/plays a file that is being played, without saving/restoring the play position.
     */
    public void pause(){
        if(this.sequencer.isRunning()) {
            this.sequencer.stop();
            this.action=Action.STOPPED;
            this.notifyObservers();
        }
    }

    /**
     * Stops a file that is being played, restoring the play position to where it was when play() was last invoked.
     * Calling stop when not playing resets the play position to the beginning of the file.
     * 
     */

    public void stop(){
        if(this.sequencer.isRunning()) {
            this.sequencer.setMicrosecondPosition(currentPosition);
            
        } else {
            this.sequencer.setMicrosecondPosition(0);
            currentPosition=0;
        }
        
        this.sequencer.stop();
        this.action=Action.STOPPED;
        this.notifyObservers();
    }

    /**
     * @return True if project is currently playing, false otherwise
     */
    public boolean isPlaying(){
        return this.sequencer.isRunning();
    }
	
	/**
	 * Sets the position of the play marker
	 * @param	microseconds	the offset from the beginning of the project (in microseconds) to which the play marker will be moved
	 */
	
	public void setPosition(long microseconds) {
		sequencer.setMicrosecondPosition(microseconds);
		
		this.notifyObservers();
	}
	
	/**
	 * Sets the position of the play marker
	 * @param	ppm	the offset from the beginning of the project (in parts per million of the project's total length, 0 - 100)
	 */
	
	public void setPPMPosition(int ppm) {
		// get the length of the project (in microseconds)
		long microsecondLength = sequencer.getMicrosecondLength();
		
		// convert from percent to microseconds
		long position = (long) Math.round(microsecondLength * ((double) ppm / 1000000));
		
		this.setPosition(position);
	}
	
	/**
	 * @return The play marker position represented in parts per million (PPM) of the project's length
	 */
	public int getPPMPosition() {
		long microsecondLength = sequencer.getMicrosecondLength();
		long microsecondPosition = sequencer.getMicrosecondPosition();
		
		int percentage = (int) Math.round(((double) microsecondPosition/microsecondLength) * 1000000);
		
		return (int) percentage;
	}
	

	/**
	 * Saves the project
	 */
	public void save(){
		// TODO: G�r n�t vettigt ;)
		this.setChanged();
		this.action = Action.SAVED;
		this.notifyObservers();
	}
	
	// TODO: Ta bort sen, anv�nds bara f�r att s�tta projektet till "changed"
	public void dummy(){
		this.setChanged();
		this.action = Action.TRACK_ADDED; // Vad som helst utom "Saved" ;)
		this.notifyObservers();
	}
}
